import consts
import datetime
import math
import requests
import time
import task


class TaskHandler:
    def __init__(self, parent, signal, params, latest):
        self.parent = parent
        self.signal = signal
        self.params = params
        self.latest = latest

    def run(self):
        raise NotImplemented


class LongBuyHandler(TaskHandler):
    def gen_task(self, quantity, rule):
        order_res = list()
        rough_qty = rule['min_quantity']
        total_qty = quantity
        upper_val = self.params.get('max_order_amount', 10000)
        price_val = self.latest['close']
        if rough_qty * price_val < upper_val:
            rough_qty = rule['min_multiple'] * math.floor(upper_val / price_val / rule['min_multiple'])
        if rough_qty > rule['max_quantity']:
            rough_qty = rule['max_quantity']
        if rough_qty > total_qty:
            rough_qty = total_qty
        order_ses = self.params.get('order_seconds', 0)
        count_val = math.ceil(total_qty / rough_qty)
        delay_ses = math.floor(order_ses / count_val)
        extra_ses = self.parent.get_delay_seconds(self.params)
        start_ses = time.time()
        for i in range(count_val):
            input_qty = rough_qty if total_qty > rough_qty else total_qty
            order_map = dict(
                timestamp=start_ses + extra_ses + i * delay_ses, signal_hex_id=self.signal['hex_id'],
                market=self.signal['market'], symbol=self.signal['symbol'],
                direction=consts.CUSTOM_DIRECTION_TYPE_LONG,
                side=consts.CUSTOM_SIDE_TYPE_BUY,
                quantity=input_qty,
                order_id=0,
                order_status=consts.XTP_ORDER_STATUS_TYPE.XTP_ORDER_STATUS_INIT.value,
                error_id=0
            )
            self.parent.task.insert(**order_map)
            order_res.append(order_map)
            total_qty = total_qty - input_qty
        return order_res

    def run(self):
        if self.parent.task.count(self.signal['hex_id']):
            return None
        position_info = self.parent.get_position_info(self.signal['symbol'])
        if position_info and position_info['total_qty']:
            return None
        rul = self.parent.get_rule(self.signal['symbol'], consts.CUSTOM_DIRECTION_TYPE_LONG, consts.CUSTOM_SIDE_TYPE_BUY)
        val = self.params.get('single_amount', 10000)
        qty = val / self.latest['close']
        qty = qty - qty % rul['min_multiple']
        res = self.gen_task(qty, rul)
        return res


class LongSellHandler(TaskHandler):
    def get_quantity(self):
        info = self.parent.get_position_info(self.signal['symbol'])
        return 0 if not info else info['sellable_qty']

    def gen_task(self, quantity, rule):
        order_res = list()
        total_qty = quantity
        rough_qty = rule['min_quantity']
        upper_val = self.params.get('max_order_amount', 10000)
        price_val = self.latest['close']
        if rough_qty * price_val < upper_val:
            rough_qty = rule['min_multiple'] * math.floor(upper_val / price_val / rule['min_multiple'])
        if rough_qty > rule['max_quantity']:
            rough_qty = rule['max_quantity']
        if rough_qty > total_qty:
            rough_qty = total_qty
        order_ses = self.params.get('order_seconds', 0)
        count_val = math.ceil(total_qty / rough_qty)
        delay_ses = math.floor(order_ses / count_val)
        extra_ses = self.parent.get_delay_seconds(self.params)
        start_ses = time.time()
        for i in range(count_val):
            input_qty = rough_qty if total_qty > rough_qty else total_qty
            order_map = dict(
                timestamp=start_ses + extra_ses + i * delay_ses, signal_hex_id=self.signal['hex_id'],
                market=self.signal['market'], symbol=self.signal['symbol'],
                direction=consts.CUSTOM_DIRECTION_TYPE_LONG,
                side=consts.CUSTOM_SIDE_TYPE_SELL,
                quantity=input_qty,
                order_id=0,
                order_status=consts.XTP_ORDER_STATUS_TYPE.XTP_ORDER_STATUS_INIT.value,
                error_id=0
            )
            self.parent.task.insert(**order_map)
            order_res.append(order_map)
            total_qty = total_qty - input_qty
        return order_res

    def run(self):
        if self.parent.task.count(self.signal['hex_id']):
            return None
        qty = self.get_quantity()
        if not qty:
            return None
        rul = self.parent.get_rule(self.signal['symbol'], consts.CUSTOM_DIRECTION_TYPE_LONG, consts.CUSTOM_SIDE_TYPE_SELL)
        res = self.gen_task(qty, rul)
        return res


class ShortBuyHandler(TaskHandler):
    def run(self):
        pass


class ShortSellHandler(TaskHandler):
    def run(self):
        pass


class Strategy:
    def __init__(self, parent):
        self.parent = parent
        self.task = task.Task(self.config.investor_id)

    @property
    def logger(self):
        return self.parent.logger

    @property
    def config(self):
        return self.parent.config

    @staticmethod
    def get_rule(symbol, direction, side):
        if direction == consts.CUSTOM_DIRECTION_TYPE_LONG:
            if side == consts.CUSTOM_SIDE_TYPE_BUY:
                if symbol.startswith('688'):
                    return {
                        'min_quantity': 200,
                        'max_quantity': 100000,
                        'min_multiple': 100
                    }
                else:
                    return {
                        'min_quantity': 100,
                        'max_quantity': 1000000,
                        'min_multiple': 100
                    }
            else:
                if symbol.startswith('688'):
                    return {
                        'min_quantity': 200,
                        'max_quantity': 100000,
                        'min_multiple': 100
                    }
                else:
                    return {
                        'min_quantity': 100,
                        'max_quantity': 1000000,
                        'min_multiple': 100
                    }
        else:
            if side == consts.CUSTOM_SIDE_TYPE_BUY:
                if symbol.startswith('688'):
                    return {
                        'min_quantity': 200,
                        'max_quantity': 100000,
                        'min_multiple': 100
                    }
                else:
                    return {
                        'min_quantity': 100,
                        'max_quantity': 1000000,
                        'min_multiple': 100
                    }
            else:
                if symbol.startswith('688'):
                    return {
                        'min_quantity': 200,
                        'max_quantity': 100000,
                        'min_multiple': 100
                    }
                else:
                    return {
                        'min_quantity': 100,
                        'max_quantity': 1000000,
                        'min_multiple': 100
                    }

    @staticmethod
    def get_today_start_timestamp():
        temp = datetime.datetime.now()
        temp = datetime.datetime(year=temp.year, month=temp.month, day=temp.day)
        return temp.timestamp()

    def get_options(self):
        href = self.config.proxy_url + '/china_stock/hosted/browse'
        data = dict(hex_id=self.config.hosted_hex_id)
        resp = requests.post(href, json=data)
        return resp.json()

    def get_signals(self, options):
        data = list()
        href = self.config.proxy_url + '/china_stock/signal/browse'
        args = dict(
            order_by='created_at',
            sort_direction=-1,
            user_hex_id=options['user_hex_id'],
            period=options['period'],
            region=options['region'],
            sector=options['sector']
        )
        resp = requests.post(href, json=args)
        resp = resp.json()
        for item in resp:
            tick = self.parent.quote_info.get(item['symbol'])
            temp = dict(
                tick=tick,
                open_deviate_rate=(tick['open'] / tick['pre_close'] - 1) * 100,
                **item
            )
            data.append(temp)
        data = sorted(data, key=lambda x: x['open_deviate_rate'])
        return data

    def get_delay_seconds(self, params):
        bygone = time.time() - self.get_today_start_timestamp()
        delays = params.get('delay_seconds', 0)
        firsts = datetime.timedelta(hours=9, minutes=30).total_seconds()
        result = delays + firsts - bygone
        return result if result > 0 else 0

    def get_available_amount(self, params):
        cash = self.parent.asset_info.get('buying_power', 0)
        frozen = params.get('frozen_amount', 0)
        return cash - frozen

    @staticmethod
    def ack_open_deviate_rate(params, latest):
        deviate = (latest['open'] / latest['pre_close'] - 1) * 100
        if 'min_open_deviate' in params:
            if deviate < params['min_open_deviate']:
                return False
        if 'max_open_deviate' in params:
            if deviate > params['max_open_deviate']:
                return False
        return True

    @staticmethod
    def ack_last_deviate_rate(params, latest):
        deviate = (latest['close'] / latest['pre_close'] - 1) * 100
        if 'min_last_deviate' in params:
            if deviate < params['min_last_deviate']:
                return False
        if 'max_last_deviate' in params:
            if deviate > params['max_last_deviate']:
                return False
        return True

    def get_position_info(self, symbol):
        return self.parent.position_info.get(symbol)

    @staticmethod
    def get_price(params, prices):
        level = params.get('price_level', 3)
        total = len(prices)
        if not total:
            return None
        if total > level:
            return prices[level]
        else:
            return prices[total - 1]

    def deposit_sell(self, item, args, tick):
        price = self.get_price(args, tick['bid'])
        price = price or tick['close']
        order_info = self.parent.req_insert_order(
            market=item['market'], symbol=item['symbol'], price=price, quantity=item['quantity'],
            side=consts.XTP_SIDE_TYPE.XTP_SIDE_SELL.value, price_type=consts.XTP_PRICE_TYPE.XTP_PRICE_LIMIT.value,
            position_effect=consts.XTP_POSITION_EFFECT_TYPE.XTP_POSITION_EFFECT_INIT.value,
            business_type=consts.XTP_BUSINESS_TYPE.XTP_BUSINESS_TYPE_MARGIN.value,
            order_client_id=item['id']
        )
        if order_info is not None:
            self.task.update(
                primary_key_id=item['id'], order_id=order_info['order_xtp_id'],
                order_status=item['order_status'], error_id=item['error_id'],
                deal_quantity=0, state=1
            )

    def deposit_buy(self, item, args, tick):
        rule = self.get_rule(item['symbol'], consts.CUSTOM_DIRECTION_TYPE_LONG, consts.CUSTOM_SIDE_TYPE_BUY)
        price = self.get_price(args, tick['ask'])
        price = price or tick['close']
        amount = self.parent.asset_info['buying_power']
        frozen = args.get('frozen_amount', 0)
        amount = amount - frozen
        target = price * item['quantity']
        if amount < target:
            lowest = args.get('min_order_amount', 10000)
            if amount > lowest:
                quantity = amount / price
                quantity = quantity - quantity % rule['min_multiple']
                quantity = int(quantity)
                if quantity < rule['min_quantity']:
                    quantity = 0
            else:
                quantity = 0
        else:
            quantity = item['quantity']
        if quantity > 0:
            order_info = self.parent.req_insert_order(
                market=item['market'], symbol=item['symbol'], price=price, quantity=quantity,
                side=consts.XTP_SIDE_TYPE.XTP_SIDE_BUY.value, price_type=consts.XTP_PRICE_TYPE.XTP_PRICE_LIMIT.value,
                position_effect=consts.XTP_POSITION_EFFECT_TYPE.XTP_POSITION_EFFECT_INIT.value,
                business_type=consts.XTP_BUSINESS_TYPE.XTP_BUSINESS_TYPE_MARGIN.value,
                order_client_id=item['id']
            )
            if order_info is not None:
                self.task.update(
                    primary_key_id=item['id'], order_id=order_info['order_xtp_id'],
                    order_status=item['order_status'], error_id=item['error_id'],
                    deal_quantity=0, state=1
                )

    def margin_buy(self, item, args, tick, info):
        price = self.get_price(args, tick['ask'])
        price = price or tick['close']
        amount = self.parent.fund_info['line_of_credit']
        target = price * item['quantity'] * float(info['fi_margin_rate'])
        if amount >= target:
            order_info = self.parent.req_insert_order(
                market=item['market'], symbol=item['symbol'], price=price, quantity=item['quantity'],
                side=consts.XTP_SIDE_TYPE.XTP_SIDE_MARGIN_TRADE.value, price_type=consts.XTP_PRICE_TYPE.XTP_PRICE_LIMIT.value,
                position_effect=consts.XTP_POSITION_EFFECT_TYPE.XTP_POSITION_EFFECT_INIT.value,
                business_type=consts.XTP_BUSINESS_TYPE.XTP_BUSINESS_TYPE_MARGIN.value,
                order_client_id=item['id']
            )
            if order_info is not None:
                self.task.update(
                    primary_key_id=item['id'], order_id=order_info['order_xtp_id'],
                    order_status=item['order_status'], error_id=item['error_id'],
                    deal_quantity=0, state=1
                )
            return order_info
        else:
            return None

    def gen_signal(self, options, signal):
        latest = signal.get('tick')
        params = options['config'][signal['direction']][signal['side']]
        if signal['direction'] == consts.CUSTOM_DIRECTION_TYPE_LONG:
            if signal['side'] == consts.CUSTOM_SIDE_TYPE_BUY:
                handler = LongBuyHandler(self, signal, params, latest)
            else:
                handler = LongSellHandler(self, signal, params, latest)
        else:
            if signal['side'] == consts.CUSTOM_SIDE_TYPE_BUY:
                handler = ShortBuyHandler(self, signal, params, latest)
            else:
                handler = ShortSellHandler(self, signal, params, latest)
        handler.run()

    def run_signal(self, options):
        signals = self.get_signals(options)
        for signal in signals:
            self.gen_signal(options, signal)

    def run_slack(self, options):
        tasks = self.task.slack()
        for item in tasks:
            args = options['config'][item['direction']][item['side']]
            outs = args.get('timeout_seconds', 120)
            outs = time.time() - outs
            if item['updated_at'] < outs:
                self.parent.req_cancel_order(item['order_id'])

    def run_again(self):
        tasks = self.task.again()
        for item in tasks:
            rule = self.get_rule(item['symbol'], item['direction'], item['side'])
            quantity = item['quantity'] - item['deal_quantity']
            if item['direction'] == consts.CUSTOM_DIRECTION_TYPE_LONG:
                if item['side'] == consts.CUSTOM_SIDE_TYPE_BUY:
                    if quantity >= rule['min_quantity']:
                        quantity = quantity - quantity % rule['min_multiple']
                    else:
                        quantity = 0
                else:
                    if item['side'] == consts.CUSTOM_SIDE_TYPE_BUY:
                        if quantity > rule['min_quantity']:
                            quantity = quantity - quantity % rule['min_multiple']
            if quantity > 0:
                order_map = dict(
                    timestamp=time.time(), signal_hex_id=item['signal_hex_id'],
                    market=item['market'], symbol=item['symbol'],
                    direction=item['direction'],
                    side=item['side'],
                    quantity=quantity,
                    order_id=0,
                    order_status=consts.XTP_ORDER_STATUS_TYPE.XTP_ORDER_STATUS_INIT.value,
                    error_id=0
                )
                self.task.insert(**order_map)
            self.task.update(
                primary_key_id=item['id'],
                deal_quantity=item['deal_quantity'],
                order_id=item['order_id'],
                order_status=item['order_status'],
                error_id=item['error_id'],
                state=2
            )

    def run_task(self, options):
        tasks = self.task.query()
        for item in tasks:
            args = options['config'][item['direction']][item['side']]
            tick = self.parent.quote_info.get(item['symbol'])
            resp = self.ack_last_deviate_rate(args, tick)
            if item['direction'] == consts.CUSTOM_DIRECTION_TYPE_LONG:
                if item['side'] == consts.CUSTOM_SIDE_TYPE_BUY:
                    if resp:
                        info = self.parent.margin_info.get(item['symbol'])
                        if not info:
                            self.deposit_buy(item, args, tick)
                        else:
                            order_info = self.margin_buy(item, args, tick, info)
                            if not order_info:
                                self.deposit_buy(item, args, tick)
                else:
                    self.deposit_sell(item, args, tick)
            else:
                if item['side'] == consts.CUSTOM_SIDE_TYPE_BUY:
                    if resp:
                        pass
                else:
                    pass

    def on_order(self, order_info, error_info):
        self.task.update(
            primary_key_id=order_info['order_client_id'], order_id=order_info['order_xtp_id'],
            order_status=order_info['order_status'], error_id=error_info['error_id'],
            deal_quantity=order_info['qty_traded'], state=1
        )

    def on_trade(self, trade_info):
        local_info = self.task.fetch(trade_info['order_client_id'])
        deal_quantity = local_info['deal_quantity'] + trade_info['quantity']
        if deal_quantity == local_info['quantity']:
            order_status = consts.XTP_ORDER_STATUS_TYPE.XTP_ORDER_STATUS_ALLTRADED.value
        else:
            order_status = local_info['order_status']
        self.task.update(
            primary_key_id=trade_info['order_client_id'], order_id=trade_info['order_xtp_id'],
            order_status=order_status, error_id=local_info['error_id'],
            deal_quantity=deal_quantity, state=1
        )

    def on_next(self, options):
        try:
            self.run_signal(options)
            self.run_slack(options)
            self.run_again()
            self.run_task(options)
        except Exception as exp:
            self.logger.error(exp)

    def start(self):
        options = self.get_options()
        self.parent.req_query_margin_info()
        self.parent.req_login()
        while True:
            if self.parent.session_id:
                self.parent.req_query_asset()
                self.parent.req_query_credit_fund_info()
                self.parent.req_query_position('')
                self.on_next(options)
            time.sleep(1)
